package com.example.facedetectionopencv;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.samples.facedetect.DetectionBasedTracker;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;

public class CameraDetectionActivity extends Activity implements CvCameraViewListener
{
	private static final String TAG = "CameraDetectionActivity";
    private static final Scalar FACE_RECT_COLOR = new Scalar(0, 255, 0, 255);
    public static final int JAVA_DETECTOR = 0;
    public static final int NATIVE_DETECTOR = 1;
    
    private MenuItem mItemFace50;
    private MenuItem mItemFace40;
    private MenuItem mItemFace30;
    private MenuItem mItemFace20;
    private MenuItem mItemType;
    
    private Mat mRGBA;
    private Mat mGRAY;
    private File mCascadeFile;
    private CascadeClassifier mCascadeClassifier;
    private DetectionBasedTracker mDetectionBasedTracker;

    private int mDetectorType = JAVA_DETECTOR;
    private String[] mDetectorName;

    private float mRelativeFaceSize = 0.2f;
    private int mAbsoluteFaceSize = 0;

    private CameraBridgeViewBase mOpenCvCameraView;
    private CheckBox eyes;
    private CheckBox nose;
    private CheckBox mouth;
    
    
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this)
    {
        @Override
        public void onManagerConnected(int status)
        {
            switch (status)
            {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");

                    // Load native library after(!) OpenCV initialization
                    System.loadLibrary("detection_based_tracker");

                    try
                    {
                        // Load cascade file from application resources
                        InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                        File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
                        mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                        FileOutputStream os = new FileOutputStream(mCascadeFile);

                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1)
                        {
                            os.write(buffer, 0, bytesRead);
                        }
                        is.close();
                        os.close();

                        mCascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
                        if (mCascadeClassifier.empty())
                        {
                            Log.e(TAG, "Failed to load cascade classifier");
                            mCascadeClassifier = null;
                        }
                        else
                        {
                            Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());
                        }
                        mDetectionBasedTracker = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);
                        cascadeDir.delete();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                        Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
                    }
                    mOpenCvCameraView.enableView();
                    break;
                }
                default:
                {
                    super.onManagerConnected(status);
                    break;
                }
            }
        }
    };

    public CameraDetectionActivity()
    {
        mDetectorName = new String[2];
        mDetectorName[JAVA_DETECTOR] = "Java";
        mDetectorName[NATIVE_DETECTOR] = "Native (tracking)";
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_camera_detection);
        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.java_camera_view);
        mOpenCvCameraView.setCvCameraViewListener(this);
        
        Button settings = (Button)findViewById(R.id.settings);
        settings.setOnClickListener(new OnClickListener()
        {
			@Override
			public void onClick(View v)
			{
				LinearLayout settings_layout = (LinearLayout)findViewById(R.id.settings_layout);
				if (settings_layout.getVisibility() == View.GONE)
				{
					settings_layout.setVisibility(View.VISIBLE);
				}
				else
				{
					settings_layout.setVisibility(View.GONE);
				}
			}
		});
        
        eyes = (CheckBox)findViewById(R.id.eyes);
        nose = (CheckBox)findViewById(R.id.nose);
        mouth = (CheckBox)findViewById(R.id.mouth);
    }

    @Override
    public void onPause()
    {
        super.onPause();
        if (mOpenCvCameraView != null)
        {
        	mOpenCvCameraView.disableView();
        }
    }

    @Override
    public void onResume()
    {
        super.onResume();
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
    }

    public void onDestroy()
    {
        super.onDestroy();
        mOpenCvCameraView.disableView();
    }

    public void onCameraViewStarted(int width, int height)
    {
        mGRAY = new Mat();
        mRGBA = new Mat();
    }

    public void onCameraViewStopped()
    {
        mGRAY.release();
        mRGBA.release();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        Log.i(TAG, "called onCreateOptionsMenu");
        mItemFace50 = menu.add("Face size 50%");
        mItemFace40 = menu.add("Face size 40%");
        mItemFace30 = menu.add("Face size 30%");
        mItemFace20 = menu.add("Face size 20%");
        mItemType = menu.add(mDetectorName[mDetectorType]);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
        if (item == mItemFace50)
        {
            setMinFaceSize(0.5f);
        }
        else if (item == mItemFace40)
        {
            setMinFaceSize(0.4f);
        }
        else if (item == mItemFace30)
        {
        	setMinFaceSize(0.3f);
        }
        else if (item == mItemFace20)
        {
        	setMinFaceSize(0.2f);
        }
        else if (item == mItemType)
        {
            mDetectorType = (mDetectorType + 1) % mDetectorName.length;
            item.setTitle(mDetectorName[mDetectorType]);
            setDetectorType(mDetectorType);
        }
        return true;
    }

    private void setMinFaceSize(float faceSize)
    {
        mRelativeFaceSize = faceSize;
        mAbsoluteFaceSize = 0;
    }

    private void setDetectorType(int type)
    {
        if (mDetectorType != type)
        {
            mDetectorType = type;

            if (type == NATIVE_DETECTOR)
            {
                Log.i(TAG, "Detection Based Tracker enabled");
                mDetectionBasedTracker.start();
            }
            else
            {
                Log.i(TAG, "Cascade detector enabled");
                mDetectionBasedTracker.stop();
            }
        }
    }

	@Override
	public Mat onCameraFrame(Mat inputFrame)
	{
		inputFrame.copyTo(mRGBA);
		Imgproc.cvtColor(inputFrame, mGRAY, Imgproc.COLOR_RGBA2GRAY);

        if (mAbsoluteFaceSize == 0)
        {
            int height = mGRAY.rows();
            if (Math.round(height * mRelativeFaceSize) > 0)
            {
                mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
            }
            mDetectionBasedTracker.setMinFaceSize(mAbsoluteFaceSize);
        }

        MatOfRect faces = new MatOfRect();

        if (mDetectorType == JAVA_DETECTOR)
        {
            if (mCascadeClassifier != null)
            {
            	mCascadeClassifier.detectMultiScale(mGRAY, faces, 1.1, 2, 2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
            			new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
            }
        }
        else if (mDetectorType == NATIVE_DETECTOR)
        {
            if (mDetectionBasedTracker != null)
            {
                mDetectionBasedTracker.detect(mGRAY, faces);
            }
        }
        else
        {
            Log.e(TAG, "Detection method is not selected!");
        }
		
        Rect[] facesArray = faces.toArray();
        for (int i = 0; i < facesArray.length; i++)
        {
        	Core.rectangle(mRGBA, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 1);
        }
        
        // Detect eyes
        if (eyes.isChecked())
        {
	        for (int i = 0; i < facesArray.length; i++)
	        {
	         	Rect r = facesArray[i];
	            Rect leftEyeArea = new Rect(r.x + r.width / 6, (int)(r.y + (r.height / 3.5)), r.width / 4, r.height / 5);
	            Rect rightEyeArea = new Rect(r.x + r.width / 2 + r.width / 10, (int)(r.y + (r.height / 3.5)), r.width / 4, r.height / 5);
	            Core.rectangle(mRGBA, leftEyeArea.tl(), leftEyeArea.br() , new Scalar(255, 0, 0, 255), 1); 
	            Core.rectangle(mRGBA, rightEyeArea.tl(), rightEyeArea.br() , new Scalar(255, 0, 0, 255), 1);
	        }
        }
        
        // Detect noses
        if (nose.isChecked())
        {
	        for (int i = 0; i < facesArray.length; i++)
	        {
	         	Rect r = facesArray[i];
	            Rect noseArea = new Rect(r.x + r.width / 2 - r.width / 8, r.y + r.height / 2 + r.height / 16, (int)(r.width / 4.2), r.height / 7);
	            Core.rectangle(mRGBA, noseArea.tl(), noseArea.br() , new Scalar(0, 0, 255, 255), 1); 
	        }
        }
        
        // Detect mouths
        if (mouth.isChecked())
        {
	        for (int i = 0; i < facesArray.length; i++)
	        {
	         	Rect r = facesArray[i];
	            Rect mouthArea = new Rect(r.x + r.width / 2 - r.width / 7, r.y + (int)(3.1 * r.height / 4), (int)(r.width / 3.2), r.height / 11);
	            Core.rectangle(mRGBA, mouthArea.tl(), mouthArea.br() , new Scalar(255, 0, 255, 255), 1); 
	        }
        }
        
        return mRGBA;
	}
}